package com.clp.protocol.iec104.server.pipeline;

import com.clp.protocol.iec104.apdu.IAsduFactory;
import com.clp.protocol.iec104.apdu.asdu.IAsdu;
import com.clp.protocol.iec104.apdu.asdu.infoobj.InfoObj;
import com.clp.protocol.iec104.definition.Tc;
import com.clp.protocol.iec104.definition.Tm;
import com.clp.protocol.iec104.definition.Ts;
import com.clp.protocol.iec104.definition.cot.Pn;
import com.clp.protocol.iec104.definition.quatype.InitCauseQuaType;
import com.clp.protocol.iec104.definition.quatype.TaQuaType;
import com.clp.protocol.iec104.server.InSlaveChannel;
import com.clp.protocol.iec104.server.SlaveChannelIAsduSender;
import com.clp.protocol.iec104.server.SlaveDataConfig;
import com.clp.protocol.iec104.server.async.SlaveChannelFuture;
import com.clp.protocol.iec104.server.async.SlaveChannelPromise;
import com.clp.protocol.iec104.server.async.SlaveFuture;
import com.clp.protocol.iec104.server.pipeline.state.data.TotalCall100DataReactor;
import com.clp.protocol.iec104.server.pipeline.state.data.TotalCall101DataReactor;
import com.clp.protocol.core.async.IFutureListener;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPromise;

import java.math.BigDecimal;
import java.util.List;

public class InternalIAsduSender implements SlaveChannelIAsduSender {
    private final PipelineManager manager;
    private final AsduSendEntrance entrance;

    private final Tm.VaryType tmVaryType; // 变化遥测报文类型
    private final Ts.VaryType tsVaryType; // 变位遥信报文类型

    public InternalIAsduSender(PipelineManager manager, AsduSendEntrance entrance, SlaveDataConfig dataConfig) {
        this.manager = manager;
        this.entrance = entrance;

        this.tmVaryType = dataConfig.getTmVaryType();
        this.tsVaryType = dataConfig.getTsVaryType();
    }

    @Override
    public InSlaveChannel slaveChannel() {
        return manager.getInSlaveChannel();
    }

    /************************************************* send ****************************************************/
    @Override
    public SlaveChannelFuture<Void> sendVaryTm(int infoObjAddr, BigDecimal val, boolean isValid) {
        SlaveChannelPromise<Void> slavePromise = slaveChannel().newVoidPromise();

        IAsdu iAsdu = null;
        switch (tmVaryType) {
            case FLOAT:
                iAsdu = IAsduFactory.getIAsduOfVaryTmFloat(rtuAddr(), infoObjAddr, val.floatValue(), isValid);
                break;
            default:
                throw new UnsupportedOperationException();
        }

        chSend(iAsdu).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()) {
                    slavePromise.setSuccess();
                } else {
                    slavePromise.setFailure(future.cause());
                }
            }
        });

        return slavePromise;
    }

    @Override
    public SlaveChannelFuture<Void> sendVaryTs(int infoObjAddr, boolean isSwitchOn,
                                               boolean isValid, boolean isCurrVal, boolean isReplaced, boolean isLocked) {
        SlaveChannelPromise<Void> slavePromise = slaveChannel().newVoidPromise();

        IAsdu iAsdu = null;
        switch (tsVaryType) {
            case ONE_POINT:
                iAsdu = IAsduFactory.getIAsduOfVaryTsOnePoint(rtuAddr(), infoObjAddr, isSwitchOn,
                        isValid, isCurrVal, isReplaced, isLocked);
                break;
            case TWO_POINT:
                iAsdu = IAsduFactory.getIAsduOfVaryTsTwoPoint(rtuAddr(), infoObjAddr, isSwitchOn,
                        isValid, isCurrVal, isReplaced, isLocked);
                break;
            default:
                throw new UnsupportedOperationException();
        }

        chSend(iAsdu).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()) {
                    slavePromise.setSuccess();
                } else {
                    slavePromise.setFailure(future.cause());
                }
            }
        });

        return slavePromise;
    }

    public ChannelFuture chSend(IAsdu iAsdu) {
        ChannelPromise promise = newChannelPromise();
        safeExecute(() -> entrance.tryChannelSend(iAsdu, promise));
        return promise;
    }

    private ChannelPromise newChannelPromise() {
        return slaveChannel().channel().newPromise();
    }

    private void safeExecute(Runnable task) {
        slaveChannel().executor().execute(task);
    }

    private int rtuAddr() {
        return manager.getInSlaveChannel().slave().rtuAddr();
    }

    /********************************************* chSend ******************************************************/
    public ChannelFuture chSendInitCompleted(InitCauseQuaType quaType) {
        return chSend(IAsduFactory.getIAsduOfInitCompleted(rtuAddr(), quaType));
    }

    /**
     * 发送总召唤确认
     *
     * @param pn
     * @return
     */
    public ChannelFuture chSendTotalCall100Ack(Pn pn) {
        return chSend(IAsduFactory.getIAsduOfTotalCall100Ack(rtuAddr(), pn));
    }

    public ChannelFuture chSendTotalCall100TmData(TotalCall100DataReactor.TmDataType tmDataType, List<InfoObj> infoObjs) {
        switch (tmDataType) {
            case FLOAT:
                return chSendTotalCall100TmDataFloat(infoObjs);
        }
        throw new UnsupportedOperationException();
    }

    private ChannelFuture chSendTotalCall100TmDataFloat(List<InfoObj> cInfoObjs) {
        return chSend(IAsduFactory.getIAsduOfTotalCall100TmDataFloat(rtuAddr(), cInfoObjs));
    }

    public ChannelFuture chSendTotalCall100TsData(TotalCall100DataReactor.TsDataType tsDataType, List<InfoObj> infoObjs) {
        switch (tsDataType) {
            case ONE_POINT:
                return chSendTotalCall100TsDataOnePoint(infoObjs);
            case TWO_POINT:
                return chSendTotalCall100TsDataTwoPoint(infoObjs);
        }
        throw new UnsupportedOperationException();
    }

    private ChannelFuture chSendTotalCall100TsDataOnePoint(List<InfoObj> infoObjs) {
        return chSend(IAsduFactory.getIAsduOfTotalCall100TsDataOnePoint(rtuAddr(), infoObjs));
    }

    private ChannelFuture chSendTotalCall100TsDataTwoPoint(List<InfoObj> infoObjs) {
        return chSend(IAsduFactory.getIAsduOfTotalCall100TsDataTwoPoint(rtuAddr(), infoObjs));
    }

    public ChannelFuture chSendTotalCall100Finished() {
        return chSend(IAsduFactory.getIAsduOfTotalCall100Finished(rtuAddr()));
    }

    public ChannelFuture chSendTotalCall101Ack(Pn pn) {
        return chSend(IAsduFactory.getIAsduOfTotalCall101Ack(rtuAddr(), pn));
    }

    public ChannelFuture chSendTotalCall101TpData(TotalCall101DataReactor.TpDataType tpDataType, List<InfoObj> infoObjs) {
        switch (tpDataType) {
            case CUMULANT:
                return chSendTotalCall101TpDataCumulant(infoObjs);
        }
        throw new UnsupportedOperationException();
    }

    public ChannelFuture chSendTotalCall101TpDataCumulant(List<InfoObj> infoObjs) {
        return chSend(IAsduFactory.getIAsduOfTotalCall101DataCumulant(rtuAddr(), infoObjs));
    }

    public ChannelFuture chSendTotalCall101Finished() {
        return chSend(IAsduFactory.getIAsduOfTotalCall101Finished(rtuAddr()));
    }

    public ChannelFuture chSendOnePointTcAck(int infoObjAddr, Tc.CmdType cmdType, Tc.OnePointSwitch onePointSwitch, Pn pn) {
        return chSend(IAsduFactory.getIAsduOfOnePointTcAck(rtuAddr(), infoObjAddr, cmdType, onePointSwitch, pn));
    }

    public ChannelFuture chSendOnePointTcFinished(int infoObjAddr, Tc.OnePointSwitch onePointSwitch) {
        return chSend(IAsduFactory.getIAsduOfOnePointTcFinished(rtuAddr(), infoObjAddr, onePointSwitch));
    }

    public ChannelFuture chSendTwoPointTcAck(int infoObjAddr, Tc.CmdType cmdType, Tc.TwoPointSwitch twoPointSwitch, Pn pn) {
        return chSend(IAsduFactory.getIAsduOfTwoPointTcAck(rtuAddr(), infoObjAddr, cmdType, twoPointSwitch, pn));
    }

    public ChannelFuture chSendTwoPointTcFinished(int infoObjAddr, Tc.TwoPointSwitch twoPointSwitch) {
        return chSend(IAsduFactory.getIAsduOfTwoPointTcFinished(rtuAddr(), infoObjAddr, twoPointSwitch));
    }

    public ChannelFuture chSendTaFloatAck(int infoObjAddr, float floatVal, TaQuaType quaType, Pn pn) {
        return chSend(IAsduFactory.getIAsduOfTaFloatAck(rtuAddr(), infoObjAddr, floatVal, quaType, pn));
    }

    public ChannelFuture chSendTaFloatFinished(int infoObjAddr, float floatVal) {
        return chSend(IAsduFactory.getIAsduOfTaFloatFinished(rtuAddr(), infoObjAddr, floatVal));
    }
}
